package com.nageoffer.jobtrain.admin.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.jobtrain.admin.common.biz.user.UserContext;
import com.nageoffer.jobtrain.admin.common.constant.RedisCacheConstant;
import com.nageoffer.jobtrain.admin.dao.entity.ContestDO;
import com.nageoffer.jobtrain.admin.dao.entity.ContestQuestionDO;
import com.nageoffer.jobtrain.admin.dao.entity.OjQuestionDO;
import com.nageoffer.jobtrain.admin.dao.mapper.ContestMapper;
import com.nageoffer.jobtrain.admin.dao.mapper.ContestQuestionMapper;
import com.nageoffer.jobtrain.admin.dao.mapper.OjQuestionMapper;
import com.nageoffer.jobtrain.admin.dto.req.ContestAddOjQuestionReqDTO;
import com.nageoffer.jobtrain.admin.dto.req.ContestDeleteOjQuestionReqDTO;
import com.nageoffer.jobtrain.admin.dto.req.ContestSortOjQuestionReqDTO;
import com.nageoffer.jobtrain.admin.service.ContestQuestionService;
import com.nageoffer.jobtrain.common.web.exception.ClientException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 竞赛题目关联接口实现层
 * author：唐晨
 */
@Service
@RequiredArgsConstructor
public class ContestQuestionServiceImpl extends ServiceImpl<ContestQuestionMapper, ContestQuestionDO> implements ContestQuestionService {
    private final StringRedisTemplate stringRedisTemplate;
    private final OjQuestionMapper ojQuestionMapper;
    private final ContestMapper contestMapper;
    private final ContestQuestionMapper contestQuestionMapper;

    /**
     * 自定义线程池
     * 属于IO密集型，这里采用CPU核心数 * 2，默认的线程工厂
     * 使用执行线程来进行执行 -拒绝策略
     */
    private static final ExecutorService executorService = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors() * 2,
            Runtime.getRuntime().availableProcessors() * 2 + 1,
            60,
            TimeUnit.SECONDS,
            new SynchronousQueue<>(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addContestOjQuestion(ContestAddOjQuestionReqDTO requestParam) {
        AtomicInteger contestDescProcess = new AtomicInteger(1);
        String contestDescStr = stringRedisTemplate.opsForValue().get(String.format(RedisCacheConstant.CONTEST_QUESTION_DESC_KEY, requestParam.getContestId()));
        if (StrUtil.isNotEmpty(contestDescStr)) {
            contestDescProcess.set(Integer.parseInt(contestDescStr));
        }
        // 验证是否有这个竞赛的基本信息
        LambdaQueryWrapper<ContestDO> queryWrapper = Wrappers.lambdaQuery(ContestDO.class)
                .eq(ContestDO::getId, requestParam.getContestId())
                .eq(ContestDO::getDelFlag, 0);
        ContestDO contestDO = contestMapper.selectOne(queryWrapper);
        if (contestDO == null) {
            throw new ClientException("请输入正确的竞赛基本信息");
        }
        // 判断竞赛是否已经开赛
        if (contestDO.getBeginTime().before(new Date())) {
            throw new ClientException("该竞赛已经开始了，禁止添加OJ题目");
        }
        // 基本ComputerFuture异步校验OJ题目是否存在
        boolean allExist = checkOjQuestionsExist(requestParam.getOjQuestionIdsList());
        if (!allExist) {
            throw new ClientException("部分OJ题目ID不存在，请检查是否存在");
        }

        List<ContestQuestionDO> contestQuestionList = new ArrayList<>();
        requestParam.getOjQuestionIdsList().forEach(each -> {
            ContestQuestionDO contestQuestionDO = ContestQuestionDO.builder()
                    .contestId(requestParam.getContestId())
                    .questionId(each)
                    .createOperator(Long.valueOf(UserContext.getAdminUserId()))
                    .sortDesc(contestDescProcess.getAndIncrement())
                    .build();
            contestQuestionList.add(contestQuestionDO);
        });
        // 更新缓存中的desc排序字段，并设置过期时间
        Date now = new Date();
        if (contestDO.getEndTime().after(now)) {
            long ttl = (contestDO.getEndTime().getTime() - now.getTime()) / 1000;
            stringRedisTemplate.opsForValue().set(String.format(RedisCacheConstant.CONTEST_QUESTION_DESC_KEY, requestParam.getContestId()),
                    String.valueOf(contestDescProcess.get()),
                    ttl,
                    TimeUnit.SECONDS);
        } else {
            throw new ClientException("当前竞赛已经结束，禁止添加OJ题目");
        }
        try {
            contestQuestionMapper.insert(contestQuestionList, contestQuestionList.size());
        } catch (Throwable ex) {
            throw new ClientException("新增竞赛OJ题目异常");
        }
    }

    private boolean checkOjQuestionsExist(Set<Long> ojQuestionIdsList) {
        // 创建CompletableFuture列表，提交异步任务
        CompletableFuture<Void> allOf = CompletableFuture.allOf(
                ojQuestionIdsList.stream()
                        .map(ojQuestionId -> CompletableFuture.runAsync(() -> {
                            // 查询每个OJ题目是否存在
                            boolean exists = checkSingleOjQuestionExist(ojQuestionId);
                            if (!exists) {
                                throw new ClientException("请检查OJ题目ID " + ojQuestionId + " 是否存在");
                            }
                        }, executorService))
                        .toArray(CompletableFuture[]::new)
        );
        // 阻塞等待所有任务完成
        try {
            allOf.join();
        } catch (Throwable ex) {
            return false;
        }
        // 所有任务执行完，返回 true
        return true;
    }

    /**
     * 存在这个OJ题目，返回 true
     */
    private boolean checkSingleOjQuestionExist(Long ojQuestionId) {
        LambdaQueryWrapper<OjQuestionDO> queryWrapper = Wrappers.lambdaQuery(OjQuestionDO.class)
                .eq(OjQuestionDO::getId, ojQuestionId)
                .eq(OjQuestionDO::getDelFlag, 0);
        return ojQuestionMapper.selectOne(queryWrapper) != null;
    }

    @Override
    public void deleteContestOjQuestion(ContestDeleteOjQuestionReqDTO requestParam) {
        // 判断该竞赛是否开赛
        LambdaQueryWrapper<ContestDO> queryWrapper = Wrappers.lambdaQuery(ContestDO.class)
                .eq(ContestDO::getId, requestParam.getContestId())
                .eq(ContestDO::getDelFlag, 0);
        if (contestMapper.selectOne(queryWrapper).getBeginTime().before(new Date())) {
            throw new ClientException("该竞赛已经开始了，禁止删除OJ题目");
        }

        LambdaUpdateWrapper<ContestQuestionDO> updateWrapper = Wrappers.lambdaUpdate(ContestQuestionDO.class)
                .eq(ContestQuestionDO::getContestId, requestParam.getContestId())
                .eq(ContestQuestionDO::getQuestionId, requestParam.getQuestionId())
                .eq(ContestQuestionDO::getDelFlag, 0);
        contestQuestionMapper.delete(updateWrapper);
    }

    @Override
    public void sortContestOjQuestion(ContestSortOjQuestionReqDTO requestParam) {
        LambdaQueryWrapper<ContestQuestionDO> queryWrapper = Wrappers.lambdaQuery(ContestQuestionDO.class)
                .eq(ContestQuestionDO::getContestId, requestParam.getId())
                .eq(ContestQuestionDO::getDelFlag, 0);
        List<ContestQuestionDO> contestQuestionList = contestQuestionMapper.selectList(queryWrapper);
        if (contestQuestionList.size() != requestParam.getOjQuestionContestList().size()) {
            throw new ClientException("重排列竞赛OJ题目参数数量异常");
        }
        requestParam.getOjQuestionContestList().forEach(each -> {
            LambdaUpdateWrapper<ContestQuestionDO> updateWrapper = Wrappers.lambdaUpdate(ContestQuestionDO.class)
                    .eq(ContestQuestionDO::getContestId, requestParam.getId())
                    .eq(ContestQuestionDO::getQuestionId, each.getId())
                    .set(ContestQuestionDO::getSortDesc, each.getSortDesc());
            contestQuestionMapper.update(updateWrapper);
        });
    }
}